import chisel3._
import Control._
import chisel3.util._
import chisel3.util.MuxLookup
import chisel3.dontTouch

class PerCountersIO extends Bundle {
  val inst        = Input(UInt(32.W))

  // event: fetch instruactions
  val ifu_ready  = Input(Bool())
  // instraction valid
  val ifu_valid   = Input(Bool())
  val ifu_ren     = Input(Bool())

  val icache_ren    = Input(Bool())
  val icache_rvalid = Input(Bool())
  val icache_rready = Input(Bool())
  val icache_rlast  = Input(Bool())

  val idu_st_type = Input(UInt(2.W))
  val idu_ld_type = Input(UInt(3.W))
  val idu_br_type = Input(UInt(3.W))
  val idu_valid   = Input(Bool())

  val meu_rvalid  = Input(Bool())
  val meu_rready  = Input(Bool())
  val meu_wvalid  = Input(Bool())
  val meu_wready  = Input(Bool())
  val meu_valid   = Input(Bool())

  val exu_valid   = Input(Bool())
  val exu_Alu_op  = Input(UInt(4.W))
  val exu_csr_cmd = Input(UInt(3.W))

  val wb_valid    = Input(Bool())
  val br_taken    = Input(Bool())
}

class FinalDisplay extends BlackBox with HasBlackBoxInline{
  val io = IO(new Bundle {
    val event_fetch_inst   = Input(UInt(64.W))
    val event_cycle_fetch  = Input(UInt(64.W))
    val event_read_data    = Input(UInt(64.W))
    val event_calculate    = Input(UInt(64.W))
    val event_load_store   = Input(UInt(64.W))
    val event_cycle_ls     = Input(UInt(64.W))
    val event_load         = Input(UInt(64.W))
    val event_cycle_load   = Input(UInt(64.W))
    val event_store        = Input(UInt(64.W))
    val event_cycle_store  = Input(UInt(64.W))
    val event_csr          = Input(UInt(64.W))
    val event_branch       = Input(UInt(64.W))
    val event_cycle_branch = Input(UInt(64.W))
    val event_cycle_total  = Input(UInt(64.W))
    val event_inst_tmt     = Input(UInt(64.W))
    val event_inst_miss    = Input(UInt(64.W))
  })

  setInline("FinalDisplay.v", """
    |module FinalDisplay(
    | input wire [63:0] event_fetch_inst,
    | input wire [63:0] event_cycle_fetch,
    | input wire [63:0] event_read_data,
    | input wire [63:0] event_calculate,
    | input wire [63:0] event_load_store,
    | input wire [63:0] event_cycle_ls,
    | input wire [63:0] event_load,
    | input wire [63:0] event_cycle_load,
    | input wire [63:0] event_store,
    | input wire [63:0] event_cycle_store,
    | input wire [63:0] event_csr,
    | input wire [63:0] event_branch,
    | input wire [63:0] event_cycle_branch,
    | input wire [63:0] event_cycle_total,
    | input wire [63:0] event_inst_tmt,
    | input wire [63:0] event_inst_miss
    |);
    |
    |final begin
    |  $display("event_fetch_inst  : %d", event_fetch_inst);
    |  $display("----------------------");
    |  $display("event_cycle_fetch : %d, proportion: %0.2f%%", event_cycle_fetch, real'(event_cycle_fetch) / real'(event_cycle_total) * 100);
    |  $display("----------------------");
    |  $display("event_read_data   : %d", event_read_data);
    |  $display("----------------------");
    |  $display("event_calculate   : %d, proportion: %0.2f%%", event_calculate, real'(event_calculate) / real'(event_fetch_inst) * 100);
    |  $display("----------------------");
    |  $display("event_load_store  : %d, proportion: %0.2f%%", event_load_store, real'(event_load_store) / real'(event_fetch_inst) * 100);
    |  $display("----------------------");
    |  $display("event_cycle_ls    : %d, proportion: %0.2f%%", event_cycle_ls, real'(event_cycle_ls) / real'(event_cycle_total) * 100);
    |  $display("----------------------");
    |  $display("event_load        : %d, proportion: %0.2f%%", event_load, real'(event_load) / real'(event_fetch_inst) * 100);
    |  $display("----------------------");
    |  $display("event_cycle_load  : %d, proportion: %0.2f%%", event_cycle_load, real'(event_cycle_load) / real'(event_cycle_total) * 100);
    |  $display("----------------------");
    |  $display("event_store       : %d, proportion: %0.2f%%", event_store, real'(event_store) / real'(event_fetch_inst) * 100);
    |  $display("----------------------");
    |  $display("event_cycle_store : %d, proportion: %0.2f%%", event_cycle_store, real'(event_cycle_store) / real'(event_cycle_total) * 100);
    |  $display("----------------------");
    |  $display("event_csr         : %d, proportion: %0.2f%%", event_csr, real'(event_csr) / real'(event_fetch_inst) * 100);
    |  $display("----------------------");
    |  $display("event_branch      : %d, proportion: %0.2f%%", event_branch, real'(event_branch) / real'(event_fetch_inst) * 100);
    |  $display("----------------------");
    |  $display("event_cycle_branch: %d, proportion: %0.2f%%", event_cycle_branch, real'(event_cycle_branch) / real'(event_cycle_total) * 100);
    |  $display("----------------------");
    |  $display("event_inst_tmt    : %d, proportion: %0.2f%%", event_inst_tmt, real'(event_inst_tmt) / real'(event_cycle_fetch) * 100);
    |  $display("----------------------");
    |  $display("event_inst_miss   : %d, proportion: %0.2f%%", event_inst_miss, (real'(event_inst_miss) / real'(event_fetch_inst)) * 100);
    |  $display("----------------------");
    |end
    |endmodule
    """.stripMargin)
}

class PerCounters extends Module {
  val io = IO(new PerCountersIO)

  val ifu_valid  = RegInit(0.B)
  val exu_valid  = RegInit(0.B)
  val ifu_ren    = RegInit(0.B)
  val icache_ren = RegInit(0.B)
  val counter    = RegInit(0.U(64.W))

  val st_valid = io.idu_st_type(1) | io.idu_st_type(0)
  val ld_valid = io.idu_ld_type(2) | io.idu_ld_type(1) | io.idu_ld_type(0)
  val br_valid = io.idu_br_type =/= BR_XXX

  val event_fetch_inst   = RegInit(0.U(64.W))
  val event_cycle_fetch  = RegInit(0.U(64.W))
  val event_read_data    = RegInit(0.U(64.W))
  val event_calculate    = RegInit(0.U(64.W))
  val event_load_store   = RegInit(0.U(64.W))
  val event_cycle_ls     = RegInit(0.U(64.W))
  val event_load         = RegInit(0.U(64.W))
  val event_cycle_load   = RegInit(0.U(64.W))
  val event_store        = RegInit(0.U(64.W))
  val event_cycle_store  = RegInit(0.U(64.W))
  val event_csr          = RegInit(0.U(64.W))
  val event_branch       = RegInit(0.U(64.W))
  val event_cycle_branch = RegInit(0.U(64.W))
  val event_cycle_total  = RegInit(0.U(64.W))
  val event_inst_tmt     = RegInit(0.U(64.W))
  val event_inst_miss    = RegInit(0.U(64.W))

  counter := Mux(io.ifu_valid, 0.U, counter + 1.U)

  ifu_ren           := Mux(io.ifu_ready, 0.B, Mux(io.ifu_ren, 1.B, ifu_ren))
  event_fetch_inst  := Mux(io.ifu_ready, event_fetch_inst + 1.U, event_fetch_inst)
  event_cycle_fetch := Mux(ifu_ren, event_cycle_fetch + 1.U, event_cycle_fetch)
  event_read_data   := Mux(io.meu_rvalid & io.meu_rready, event_read_data  + 1.U, event_read_data)

  // currently, instruactions related to CSR have not perform operations (add, sub and so on)
  exu_valid        := io.exu_valid
  event_calculate  := Mux(!(!exu_valid & io.exu_valid & (io.exu_csr_cmd === CSR_N)), event_calculate, MuxLookup(io.exu_Alu_op, event_calculate)(
    Seq(ALU_ADD  -> (event_calculate + 1.U),
        ALU_CP_A -> event_calculate,
        ALU_CP_B -> event_calculate,
        ALU_SLTU -> (event_calculate + 1.U),
        ALU_SUB  -> (event_calculate + 1.U),
        ALU_XOR  -> (event_calculate + 1.U),
        ALU_SRA  -> (event_calculate + 1.U),
        ALU_AND  -> (event_calculate + 1.U),
        ALU_SL   -> (event_calculate + 1.U),
        ALU_OR   -> (event_calculate + 1.U),
        ALU_SRL  -> (event_calculate + 1.U),
        ALU_SLT  -> (event_calculate + 1.U)
        )))

  event_load_store  := Mux((io.meu_rvalid & io.meu_rready) | (io.meu_wvalid & io.meu_wready), event_load_store + 1.U, event_load_store)
  event_cycle_ls    := Mux(io.meu_valid & (st_valid | ld_valid), event_cycle_ls + 1.U, event_cycle_ls)

  event_load        := Mux((io.meu_rvalid & io.meu_rready), event_load + 1.U, event_load)
  event_cycle_load  := Mux(io.meu_valid & ld_valid, event_cycle_load + 1.U, event_cycle_load)

  event_store       := Mux(io.meu_wvalid & io.meu_wready, event_store + 1.U, event_store)
  event_cycle_store := Mux(io.meu_valid & st_valid, event_cycle_store + 1.U, event_cycle_store)

  event_csr := Mux(!(!exu_valid & io.exu_valid & (io.exu_csr_cmd =/= CSR_N)), event_csr, event_csr + 1.U)

  event_branch       := Mux(io.idu_valid & br_valid, event_branch + 1.U, event_branch)
  event_cycle_branch := Mux(io.wb_valid & io.br_taken, event_cycle_branch + counter + 1.U, event_cycle_branch)

  event_cycle_total  := event_cycle_total + 1.U

  icache_ren         := Mux(io.icache_rvalid & io.icache_rready & io.icache_rlast, 0.B, Mux(io.icache_ren, 1.B, icache_ren))
  event_inst_tmt     := Mux(icache_ren, event_inst_tmt + 1.U, event_inst_tmt)
  event_inst_miss    := Mux(io.icache_rvalid & io.icache_rready, event_inst_miss + 1.U, event_inst_miss)

  val finalDisplay = Module(new FinalDisplay)

  finalDisplay.io.event_fetch_inst   := event_fetch_inst
  finalDisplay.io.event_cycle_fetch  := event_cycle_fetch
  finalDisplay.io.event_read_data    := event_read_data
  finalDisplay.io.event_calculate    := event_calculate
  finalDisplay.io.event_load_store   := event_load_store
  finalDisplay.io.event_cycle_ls     := event_cycle_ls
  finalDisplay.io.event_load         := event_load
  finalDisplay.io.event_cycle_load   := event_cycle_load
  finalDisplay.io.event_store        := event_store
  finalDisplay.io.event_cycle_store  := event_cycle_store
  finalDisplay.io.event_csr          := event_csr
  finalDisplay.io.event_branch       := event_branch
  finalDisplay.io.event_cycle_branch := event_cycle_branch
  finalDisplay.io.event_cycle_total  := event_cycle_total
  finalDisplay.io.event_inst_tmt     := event_inst_tmt
  finalDisplay.io.event_inst_miss    := event_inst_miss

  dontTouch(event_fetch_inst)
  dontTouch(event_cycle_fetch)
  dontTouch(event_read_data)
  dontTouch(event_calculate)
  dontTouch(event_load_store)
  dontTouch(event_cycle_ls)
  dontTouch(event_load)
  dontTouch(event_cycle_load)
  dontTouch(event_store)
  dontTouch(event_cycle_store)
  dontTouch(event_csr)
  dontTouch(event_branch)
  dontTouch(event_cycle_branch)
  dontTouch(event_cycle_total)
  dontTouch(event_inst_tmt)
  dontTouch(event_inst_miss)
}
